package net.sf.jhighlighter.kits;

import java.awt.*;
import java.util.regex.*;
import javax.swing.text.*;

class HTMLDocument extends DefaultStyledDocument {

    private MutableAttributeSet style;
    private Element rootElement;
    private Pattern multiLineCommentDelimiterStart = Pattern.compile("<");
    private Pattern multiLineCommentDelimiterEnd = Pattern.compile(">");
    private Pattern docTypeDelimiterStart = Pattern.compile("<!DOCTYPE");
    private Pattern commentDelimiterStart = Pattern.compile("<!--");
    private Pattern commentDelimiterEnd = Pattern.compile("-->");
    private MutableAttributeSet quote;

    public HTMLDocument() {
        putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
        rootElement = getDefaultRootElement();
        style = new SimpleAttributeSet();

        quote = new SimpleAttributeSet();
        StyleConstants.setForeground(quote, Color.MAGENTA);
    }

    @Override
    public void insertString(int offset, String str, AttributeSet attr) throws
            BadLocationException {
        if (str.equals("<")) {
            str = "<>";
        } else if (str.equals("\"")) {
            str = "\"\"";
        } else if (str.equals("\n")) {
            str = addWhiteSpace(offset);
        }
        super.insertString(offset, str, attr);
        processChangedLines(offset, str.length());
    }

    @Override
    public void remove(int offset, int length) throws BadLocationException {
        super.remove(offset, length);
        processChangedLines(offset, length);
    }

    public void processChangedLines(int offset, int length) throws BadLocationException {
        String text = getText(0, getLength());
        highlightString(Color.black, 0, getLength(), true, false);

        Matcher mlcStart = multiLineCommentDelimiterStart.matcher(text);
        Matcher mlcEnd = multiLineCommentDelimiterEnd.matcher(text);

        while (mlcStart.find()) {
            if (mlcEnd.find(mlcStart.end())) {
                highlightString(Color.BLUE, mlcStart.start(), (mlcEnd.end() - mlcStart.start()), true, true);
            } else {
                highlightString(Color.BLUE, mlcStart.start(), getLength(), true, true);
            }
        }

        Matcher docStart = docTypeDelimiterStart.matcher(text);

        while (docStart.find()) {
            if (mlcEnd.find(docStart.end())) {
                highlightString(new Color(100, 149, 237), docStart.start(), (mlcEnd.end() - docStart.start()), true, true);
            } else {
                highlightString(Color.BLUE, docStart.start(), getLength(), true, true);
            }
        }

        Matcher cStart = commentDelimiterStart.matcher(text);
        Matcher cEnd = commentDelimiterEnd.matcher(text);

        while (cStart.find()) {
            if (cEnd.find(cStart.end())) {
                highlightString(Color.lightGray, cStart.start(), (cEnd.end() - cStart.start()), true, true);
            } else {
                highlightString(Color.lightGray, cStart.start(), getLength(), true, true);
            }
        }

        String content = getText(0, getLength());
        // The lines affected by the latest document update
        int startLine = rootElement.getElementIndex(offset);
        int endLine = rootElement.getElementIndex(offset + length);

        // Make sure all comment lines prior to the start line are commented
        // and determine if the start line is still in a multi line comment
        // Do the actual highlighting
        for (int i = startLine; i <= endLine; i++) {
            applyHighlighting(content, i);
        }
    }

    private void applyHighlighting(String content, int line)
            throws BadLocationException {
        int startOffset = rootElement.getElement(line).getStartOffset();
        int endOffset = rootElement.getElement(line).getEndOffset() - 1;
        int contentLength = content.length();
        if (endOffset >= contentLength) {
            endOffset = contentLength - 1;
        }
        // check for tokens
        checkForTokens(content, startOffset, endOffset);
    }

    private void checkForTokens(String content, int startOffset, int endOffset) {
        while (startOffset <= endOffset) {
            // skip the delimiters to find the start of a new token
            while (isDelimiter(content.substring(startOffset, startOffset + 1))) {
                if (startOffset < endOffset) {
                    startOffset++;
                } else {
                    return;
                }
            }
            // Extract and process the entire token
            if (isQuoteDelimiter(content.substring(startOffset, startOffset + 1))) {
                startOffset = getQuoteToken(content, startOffset, endOffset);
            } else {
                startOffset = getOtherToken(content, startOffset, endOffset);
            }
        }
    }

    private int getQuoteToken(String content, int startOffset, int endOffset) {
        String quoteDelimiter = content.substring(startOffset, startOffset + 1);
//        String escapeString = getEscapeString(quoteDelimiter);
        int index;
        int endOfQuote = startOffset;
        int endOfToken = startOffset + 1;
        while (endOfToken <= endOffset) {
            if (isDelimiter(content.substring(endOfToken, endOfToken + 1))) {
                break;
            }
            endOfToken++;
        }

        // now find the matching delimiter
        index = content.indexOf(quoteDelimiter, endOfQuote + 1);
        if ((index < 0) || (index > endOffset)) {
            endOfQuote = endOffset;
        } else {
            endOfQuote = index;
        }
        setCharacterAttributes(startOffset, endOfQuote - startOffset + 1,
                quote, false);
        return endOfQuote + 1;
    }

    private int getOtherToken(String content, int startOffset, int endOffset) {
        int endOfToken = startOffset + 1;
        while (endOfToken <= endOffset) {
            if (isDelimiter(content.substring(endOfToken, endOfToken + 1))) {
                break;
            }
            endOfToken++;
        }
        return endOfToken + 1;
    }

    protected boolean isDelimiter(String character) {
        String operands = ".;:()\\[]+-/%<=>!&|^~*";
        if (Character.isWhitespace(character.charAt(0)) ||
                operands.indexOf(character) != -1) {
            return true;
        } else {
            return false;
        }
    }

    protected boolean isQuoteDelimiter(String character) {
        String quoteDelimiters = "\"";
        if (quoteDelimiters.indexOf(character) < 0) {
            return false;
        } else {
            return true;
        }
    }

    public void highlightString(Color col, int begin, int length, boolean flag, boolean bold) {
        StyleConstants.setForeground(style, col);

        setCharacterAttributes(begin, length, style, flag);
    }

    public void highlightKeyWordString(Color col, int begin, int length, boolean flag, boolean bold) {
        StyleConstants.setForeground(style, col);
        StyleConstants.setBold(style, true);

        setCharacterAttributes(begin, length, style, flag);
    }

    protected String addWhiteSpace(int offset) throws BadLocationException {
        StringBuffer whiteSpace = new StringBuffer();
        int line = rootElement.getElementIndex(offset);
        int i = rootElement.getElement(line).getStartOffset();
        while (true) {
            String temp = getText(i, 1);
            if (temp.equals(" ") || temp.equals("\t")) {
                whiteSpace.append(temp);
                i++;
            } else {
                break;
            }
        }
        return "\n" + whiteSpace;
    }
}
